/* azureProvisioningClientSample.c - Azure IoT Device provisioning client sample */

// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT

/* Copyright (c) 2021 Wind River Systems, Inc. */

/*
DESCRIPTION
This sample utilizes MQTT implementation provided by Eclipse Mosquitto and
shows how to use "Azure SDK for Embedded C" to provision a device to
Azure IoT Hub.

Environment setup:
1. Follow this guide:
https://docs.microsoft.com/en-us/azure/iot-dps/quick-setup-auto-provision
to create a Azure IoT Device Provisioning Service and link it to your
Azure IoT Hub.
2. Follow this README:
https://github.com/Azure/azure-sdk-for-c/blob/main/sdk/samples/iot/README.md
to create "Individual Enrollments" with SAS or X509.
3. Follow this guide:
https://github.com/Azure/azure-iot-sdk-c/blob/master/tools/CACertificates
/CACertificateOverview.md
to create a private X509 CA certificate.

This sample shows how to provision a device with SAS or X509 to
Azure IoT Hub as a provisioning client.
*/

/* includes */

#include <azure/az_core.h>
#include <azure/az_iot.h>
#include <mosquitto.h>
#include <stdio.h>
#include <sysLib.h>
#include <taskLib.h>
#include <time.h>

#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>

/* defines */

/*
 * Define X509_CERTIFICATE to use user-generated Root Certificate.
 * If you only want to use SAS authentication, undefine or comment out this macro.
 */
/* #define X509_CERTIFICATE */

#define PORT 8883

/*
 * Host name can be found via:
 * Azure IoT Device Provisioning Service -> Overview -> Global device endpoint
 */
#define HOST "global.azure-devices-provisioning.net"

/*
 * Server certificate. It can be found in:
 * https://github.com/Azure/azure-iot-sdk-c/blob/master/certs/certs.c
 *
 * Copy all the sections beginning with "-----BEGIN CERTIFICATE-----" and
 * ending with "-----END CERTIFICATE-----" into a file with an extension "pem"
 * like "certs.pem". Remove all the beginning and ending double quotes and
 * "\r\n".
 * -----BEGIN CERTIFICATE-----
 * MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
 * RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
 * ..............................
 * ..............................
 * Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
 * ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
 * R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
 * -----END CERTIFICATE-----
 */
#define CERT_FILE "/romfs/certs/cert.pem"

/*
 * X509 user-generated Root Certificate guide:
 * https://github.com/Azure/azure-iot-sdk-c/blob/master/tools
 * /CACertificates/CACertificateOverview.md
 */
#ifdef X509_CERTIFICATE
#define X509_CERT_FILE "/romfs/certs/provisioningx509.pem"
#define X509_KEY_FILE "/romfs/certs/provisioningx509key.pem"
#else
#define X509_CERT_FILE NULL
#define X509_KEY_FILE NULL
#endif

#define SAS_KEY_DURATION_SECONDS 3600 /* one hour */

/*
 * After 240 seconds, the broker should send a PING message to the client
 * if no other messages have been exchanged in that time.
 */
#define MQTT_KEEPALIVE_SECS 240

/* locals */

static const int mqttVer = MQTT_PROTOCOL_V311;

/*
 * Registration ID is the name of each enrollment. Can be found via:
 * Azure IoT Device Provisioning Service -> Manage Enrollments
 *  -> Individual Enrollment
 */
#ifdef X509_CERTIFICATE
static az_span const provisioningRegId = AZ_SPAN_LITERAL_FROM_STR("VxNodeProvisioningX509");
#else
static az_span const provisioningRegId = AZ_SPAN_LITERAL_FROM_STR("symm-key-node");
#endif /* X509_CERTIFICATE */

/*
 * ID Scope can be found via:
 * Azure IoT Device Provisioning Service -> Overview -> ID Scope
 */
static az_span const provisioningIdScope = AZ_SPAN_LITERAL_FROM_STR("0ne00XXXXXX");

/*
 * Global Endpoint can be found via:
 * Azure IoT Device Provisioning Service -> Overview -> Global device endpoint
 * and add prefix "ssl://" as well as suffix ":8883"
 */
static char provisioningGlobalEndpoint[] = "ssl://" HOST ":8883";

/*
 * Password is only needed by SAS enrollment. It is generated by DPS Primary Key.
 * For X509 enrollment, it is NULL.
 */
#ifdef X509_CERTIFICATE
static char* pwd = NULL;
#else /* X509_CERTIFICATE */
static char pwd[256] = { 0 };

/*
 * Primary Key is only needed by SAS enrollment.
 * It can be found via:
 * Azure IoT Device Provisioning Service -> Manage Enrollments
 *  -> Individual Enrollment -> one specific device enrollment -> Primary Key
 */
static char provisioningPrimaryKey[] = "ch/EsBKjGNEHLc......";
#endif /* X509_CERTIFICATE */

/*******************************************************************************
 *
 * mqttPublish - publish a message to Azure IoT DPS service
 *
 * This routine publishes a messsage to Azure IoT Device Provisioning Service.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if the publication is successful, otherwise false
 *
 * \NOMANUAL
 */

static bool mqttPublish(
    struct mosquitto* mqttClient, /* MQTT client object */
    int* msgId, /* message ID */
    char* topic, /* topic to publish */
    int payloadLen, /* payload length */
    void* payload /* payload data */
)
{
  int res;
  res = mosquitto_publish(mqttClient, msgId, topic, payloadLen, payload, 1, true);
  if (res == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_publish: %d OK.\n", *msgId);
  }
  else
  {
    printf_s("mosquitto_publish ERROR: %s\n", mosquitto_strerror(res));
    return false;
  }
  return true;
}

/*******************************************************************************
 *
 * onConnect - connect callback of a MQTT client
 *
 * This routine is called when a MQTT client is connected to the
 * Azure IoT Device Provisioning Service.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void onConnect(
    struct mosquitto* mqttClient, /* MQTT client object */
    void* obj, /* user data provided in mosquitto_new */
    int result /* result of the connection */
)
{
  if (result == MOSQ_ERR_SUCCESS)
  {
    printf_s("connect OK.\n");
  }
  else
  {
    printf_s("connect ERROR: %s\n", mosquitto_strerror(result));
  }
}

/*******************************************************************************
 *
 * onPublish - publish callback of a MQTT client
 *
 * This routine is called when a MQTT client finishes sending a message to the
 * Azure IoT Device Provisioning Service.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void onPublish(
    struct mosquitto* mqttClient, /* MQTT client object */
    void* obj, /* user data provided in mosquitto_new */
    int msgId /* message ID */
)
{
  printf_s("Message: %d is sent.\n", msgId);
}

/*******************************************************************************
 *
 * queryStatusSend - publish a query status message to a DPS
 *
 * This routine publishes a query status message to the Device Provisioning
 * Service to check whether a device provisioning operation is completed or not.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void queryStatusSend(
    az_iot_provisioning_client* azureClient, /* provisioning client */
    struct mosquitto* mqttClient, /* MQTT client object */
    az_iot_provisioning_client_register_response* response /* response */
)
{
  char queryTopic[256];
  int msgId;

  if (az_result_failed(az_iot_provisioning_client_query_status_get_publish_topic(
          azureClient, response->operation_id, queryTopic, sizeof(queryTopic), NULL)))
  {
    printf_s("az_iot_provisioning_client_query_status_get_publish_topic "
             "ERROR\n");
    return;
  }

  printf_s("Will retry, after recommended %d seconds\n", response->retry_after_seconds);
  (void)taskDelay((_Vx_ticks_t)response->retry_after_seconds * (_Vx_ticks_t)sysClkRateGet());
  (void)mqttPublish(mqttClient, &msgId, queryTopic, 0, NULL);
}

/*******************************************************************************
 *
 * deviceRegistrationStatusHandle - handle a device registration status response
 *
 * This routine handles a device registration status response.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void deviceRegistrationStatusHandle(
    az_iot_provisioning_client* azureClient, /* provisioning client */
    struct mosquitto* mqttClient, /* MQTT client object */
    az_iot_provisioning_client_register_response* response /* response */
)
{
  if (az_iot_provisioning_client_operation_complete(response->operation_status))
  {
    printf_s("Device registration is completed.\n");
    if (response->operation_status == AZ_IOT_PROVISIONING_STATUS_ASSIGNED)
    {
      printf_s("Device is successfully provisioned.\n");
    }
    else
    {
      printf_s("Device provisioning failed with status %d!\n", response->operation_status);
    }
    (void)mosquitto_disconnect(mqttClient);
  }
  else
  {
    /*
     * if the Registration is not finished, send a status query request to
     * check again.
     */
    printf_s("Device registration is in progress.\n");
    queryStatusSend(azureClient, mqttClient, response);
  }
}

/*******************************************************************************
 *
 * onMessage - message receiving callback of a MQTT client
 *
 * This routine is called when a MQTT client receives a message.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void onMessage(
    struct mosquitto* mqttClient, /* MQTT client object */
    void* obj, /* user data provided */
    /* in mosquitto_new */
    const struct mosquitto_message* message /* message object */
)
{
  az_span topic;
  az_span msgSpan;
  az_iot_provisioning_client* azureClient;
  az_iot_provisioning_client_register_response registerRsp;

  if (message == NULL)
  {
    printf_s("onMessage: message is NULL!\n");
    return;
  }
  topic = az_span_create_from_str(message->topic);
  msgSpan = az_span_create((uint8_t*)(message->payload), message->payloadlen);

  if (obj != NULL)
  {
    azureClient = (az_iot_provisioning_client*)obj;
  }
  else
  {
    printf_s("onMessage: obj is NULL!\n");
    return;
  }

  if (az_result_succeeded(az_iot_provisioning_client_parse_received_topic_and_payload(
          azureClient, topic, msgSpan, &registerRsp)))
  {
    deviceRegistrationStatusHandle(azureClient, mqttClient, &registerRsp);
  }
}

#ifndef X509_CERTIFICATE

/*******************************************************************************
 *
 * base64Decode - decode a base64 string to a binary buffer
 *
 * This routine decodes a base64 string to a binary buffer.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if the decoding is successful, false otherwise
 *
 * \NOMANUAL
 */

static bool base64Decode(az_span base64Encoded, az_span* decoded)
{
  BIO* base64Decoder = NULL;
  BIO* sourceMemBio = NULL;
  int readCnt = 0;

  (void)memset_s(
      az_span_ptr(*decoded), (size_t)az_span_size(*decoded), 0, (size_t)az_span_size(*decoded));

  /* Create a base64 BIO filter */
  base64Decoder = BIO_new(BIO_f_base64());
  if (base64Decoder == NULL)
  {
    printf_s("BIO_new ERROR!\n");
    goto base64DecodeFailed;
  }

  /* Create a source BIO memory */
  sourceMemBio = BIO_new_mem_buf(az_span_ptr(base64Encoded), (int)az_span_size(base64Encoded));
  if (sourceMemBio == NULL)
  {
    printf_s("BIO_new_mem_buf ERROR!\n");
    goto base64DecodeFailed;
  }

  /* push the base64 encoded buffer through the base64 filter */
  sourceMemBio = BIO_push(base64Decoder, sourceMemBio);
  if (sourceMemBio == NULL)
  {
    printf_s("BIO_push ERROR!\n");
    goto base64DecodeFailed;
  }

  /* Do not have a newline and close the BIO */
  BIO_set_flags(sourceMemBio, BIO_FLAGS_BASE64_NO_NL);
  BIO_set_close(sourceMemBio, BIO_CLOSE);

  readCnt = BIO_read(sourceMemBio, az_span_ptr(*decoded), (int)az_span_size(*decoded));
  if (readCnt <= 0)
  {
    printf_s("BIO_read ERROR!\n");
    goto base64DecodeFailed;
  }
  *decoded = az_span_create(az_span_ptr(*decoded), (int32_t)readCnt);
  BIO_free_all(sourceMemBio);
  return true;

base64DecodeFailed:
  if (base64Decoder != NULL)
  {
    BIO_free(base64Decoder);
  }

  if (sourceMemBio != NULL)
  {
    BIO_free(sourceMemBio);
  }

  return false;
}

/*******************************************************************************
 *
 * base64Decode - encode a binary buffer to a base64 string
 *
 * This routine encodes a binary buffer to a base64 string.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if the encoding is successful, false otherwise
 *
 * \NOMANUAL
 */

static bool base64Encode(az_span hmac256SignedSignature, az_span* base64SignedSignature)
{
  BIO* base64Encoder = NULL;
  BIO* sinkMemBio = NULL;
  BUF_MEM* encodedMemPtr = NULL;
  int writeCnt = 0;

  base64Encoder = BIO_new(BIO_f_base64());
  if (base64Encoder == NULL)
  {
    printf_s("BIO_new base64 encoder ERROR!\n");
    goto base64EncodeFailed;
  }

  sinkMemBio = BIO_new(BIO_s_mem());
  if (sinkMemBio == NULL)
  {
    printf_s("BIO_new sink memory ERROR!\n");
    goto base64EncodeFailed;
  }

  base64Encoder = BIO_push(base64Encoder, sinkMemBio);
  if (base64Encoder == NULL)
  {
    printf_s("BIO_push base64 encoder ERROR!\n");
    goto base64EncodeFailed;
  }

  BIO_set_flags(base64Encoder, BIO_FLAGS_BASE64_NO_NL);

  writeCnt = BIO_write(
      base64Encoder,
      az_span_ptr(hmac256SignedSignature),
      (int)az_span_size(hmac256SignedSignature));
  if (writeCnt < 1)
  {
    printf_s("BIO_write ERROR!\n");
    goto base64EncodeFailed;
  }

  BIO_flush(base64Encoder);
  BIO_get_mem_ptr(base64Encoder, &encodedMemPtr);

  if (encodedMemPtr == NULL)
  {
    printf_s("BIO_get_mem_ptr ERROR!\n");
    goto base64EncodeFailed;
  }
  if (encodedMemPtr->length > az_span_size(*base64SignedSignature))
  {
    printf_s("Signed Signature buffer doesn't have enough space!\n");
    goto base64EncodeFailed;
  }

  (void)memcpy_s(
      az_span_ptr(*base64SignedSignature),
      (size_t)az_span_size(*base64SignedSignature),
      encodedMemPtr->data,
      (size_t)encodedMemPtr->length);
  *base64SignedSignature
      = az_span_create(az_span_ptr(*base64SignedSignature), (int32_t)encodedMemPtr->length);
  BIO_free_all(base64Encoder);
  return true;
base64EncodeFailed:
  if (base64Encoder != NULL)
  {
    BIO_free(base64Encoder);
  }
  if (sinkMemBio != NULL)
  {
    BIO_free(sinkMemBio);
  }
  return false;
}

/*******************************************************************************
 *
 * hmacSha256SignatureSign - generate a HMAC-SHA256 signature
 *
 * This routine generates a HMAC-SHA256 signature.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if successful, false otherwise
 *
 * \NOMANUAL
 */

static bool hmacSha256SignatureSign(az_span decodedKey, az_span signature, az_span* signedSignature)
{
  unsigned int mdLen;

  unsigned char* hmac = HMAC(
      EVP_sha256(),
      az_span_ptr(decodedKey),
      az_span_size(decodedKey),
      az_span_ptr(signature),
      (size_t)az_span_size(signature),
      az_span_ptr(*signedSignature),
      &mdLen);
  if (hmac == NULL)
  {
    printf_s("HMAC ERROR!\n");
    return false;
  }
  *signedSignature = az_span_create(az_span_ptr(*signedSignature), mdLen);
  return true;
}

/*******************************************************************************
 *
 * mqttPasswordGen - generate a MQTT password when using Shared Access Key
 *
 * This routine generates a MQTT password when using Shared Access Key.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if successful, false otherwise
 *
 * \NOMANUAL
 */

static bool mqttPasswordGen(
    az_iot_provisioning_client* azureClient, /* provisioning client obj */
    char* pwdBuffer, /* password buffer */
    size_t pwdBufferLen /* password buffer length */
)
{
  static char sasSignature[128] = { 0 };
  static char sasBase64SignedSignature[128] = { 0 };
  /* calculate duration time */
  uint64_t sasKeyExpirationTime = (uint64_t)time(NULL) + SAS_KEY_DURATION_SECONDS;
  az_span sasBase64SignedSignatureSpan = AZ_SPAN_FROM_BUFFER(sasBase64SignedSignature);
  az_span provisioningPrimaryKeySpan = AZ_SPAN_FROM_BUFFER(provisioningPrimaryKey);
  static char sasDecodedKey[64] = { 0 };
  az_span sasDecodedKeySpan = AZ_SPAN_FROM_BUFFER(sasDecodedKey);
  static char sasHMAC256SignedSignature[128] = { 0 };
  az_span sasHMAC256SignedSignatureSpan = AZ_SPAN_FROM_BUFFER(sasHMAC256SignedSignature);
  size_t pwdLen;

  /* get the signature which will be signed with the decoded key */
  az_span sasSignatureSpan = AZ_SPAN_FROM_BUFFER(sasSignature);
  if (az_result_failed(az_iot_provisioning_client_sas_get_signature(
          azureClient, sasKeyExpirationTime, sasSignatureSpan, &sasSignatureSpan)))
  {
    printf_s("az_iot_provisioning_client_sas_get_signature ERROR!\n");
    return false;
  }

  if (!base64Decode(provisioningPrimaryKeySpan, &sasDecodedKeySpan))
  {
    return false;
  }

  if (!hmacSha256SignatureSign(sasDecodedKeySpan, sasSignatureSpan, &sasHMAC256SignedSignatureSpan))
  {
    return false;
  }

  if (!base64Encode(sasHMAC256SignedSignatureSpan, &sasBase64SignedSignatureSpan))
  {
    return false;
  }

  /* get the MQTT password */
  if (az_result_failed(az_iot_provisioning_client_sas_get_password(
          azureClient,
          sasBase64SignedSignatureSpan,
          sasKeyExpirationTime,
          AZ_SPAN_EMPTY,
          pwdBuffer,
          pwdBufferLen,
          &pwdLen)))
  {
    printf_s("az_iot_provisioning_client_sas_get_password ERROR!\n");
    return false;
  }

  return true;
}
#endif /* X509_CERTIFICATE */

/*******************************************************************************
 *
 * azureProvisioningClientCreate - create an Azure IoT device provisioning client object
 *
 * This routine creates and initializes an Azure IoT Device Provisioning
 * client object. The user name and client ID are retrieved for
 * the further use. The password is generated for SAS.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if successful, otherwise false
 *
 * \NOMANUAL
 */

static bool azureProvisioningClientCreate(
    az_iot_provisioning_client* azureClient, /* Azure provisioning */
    /* client object */
    char* azureUserName, /* user name buffer */
    size_t azureUserNameLen, /* size of user name */
    char* azureClientId, /* client ID buffer */
    size_t azureClientIdLen /* size of client ID */
)
{
  az_result azureRes;
  az_span globalDeviceEndpoint = az_span_create_from_str(provisioningGlobalEndpoint);

  /* init Azure Provisioning Client */
  azureRes = az_iot_provisioning_client_init(
      azureClient, globalDeviceEndpoint, provisioningIdScope, provisioningRegId, NULL);
  if (az_result_succeeded(azureRes))
  {
    printf_s("az_iot_provisioning_client_init OK.");
  }
  else
  {
    printf_s("az_iot_provisioning_client_init ERROR!\n");
    return false;
  }

  azureRes = az_iot_provisioning_client_get_client_id(
      azureClient, azureClientId, azureClientIdLen, NULL);
  if (az_result_succeeded(azureRes))
  {
    printf_s("az_iot_provisioning_client_get_client_id OK: %s\n", azureClientId);
  }
  else
  {
    printf_s("az_iot_provisioning_client_get_client_id ERROR!\n");
    return false;
  }

#ifndef X509_CERTIFICATE
  /* generate a password for MQTT */
  if (!mqttPasswordGen(azureClient, pwd, sizeof(pwd)))
  {
    return false;
  }
#endif

  azureRes = az_iot_provisioning_client_get_user_name(
      azureClient, azureUserName, azureUserNameLen, NULL);
  if (az_result_succeeded(azureRes))
  {
    printf_s("az_iot_provisioning_client_get_user_name OK: %s\n", azureUserName);
  }
  else
  {
    printf_s("az_iot_provisioning_client_get_user_name ERROR!\n");
    return false;
  }

  return true;
}

/*******************************************************************************
 *
 * mqttClientDestroy - destroy a MQTT client object
 *
 * This routine first disconnects the MQTT client from the Azure IoT device
 * provisioning service, destroys the mosquitto object and does the library
 * cleanup for mosquitto.
 *
 * ERRNO: N/A
 *
 * RETURNS: void
 *
 * \NOMANUAL
 */

static void mqttClientDestroy(struct mosquitto* mqttClient /* MQTT client object */
)
{
  if (mqttClient != NULL)
  {
    (void)mosquitto_disconnect(mqttClient);
    mosquitto_destroy(mqttClient);
    mqttClient = NULL;
    (void)mosquitto_lib_cleanup();
  }
}

/*******************************************************************************
 *
 * mqttClientCreate - create a MQTT client object
 *
 * This routine creates a MQTT client object and connects it to the
 * Azure IoT device provisioning service.
 *
 * ERRNO: N/A
 *
 * RETURNS: a pointer to the MQTT client object if successful, otherwise NULL
 *
 * \NOMANUAL
 */

static struct mosquitto* mqttClientCreate(
    az_iot_provisioning_client* azureClient, /* provisioning client obj */
    char* azureUserName, /* user name */
    char* azureClientId /* client ID */
)
{
  int mqttRes;
  struct mosquitto* mqttClient = NULL;

  /* init the mosquitto lib */
  mqttRes = mosquitto_lib_init();
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_lib_init() OK\n");
  }
  else
  {
    printf_s("mosquitto_lib_init ERROR: %s\n", mosquitto_strerror(mqttRes));
    return NULL;
  }

  /* create an MQTT client */
  mqttClient = mosquitto_new(azureClientId, true, &azureClient);
  if (mqttClient != NULL)
  {
    printf_s("mosquitto_new OK.\n");
  }
  else
  {
    printf_s("mosquitto_new ERROR: %s\n", mosquitto_strerror(mqttRes));
    (void)mosquitto_lib_cleanup();
    return NULL;
  }

  /* add callbacks */
  mosquitto_connect_callback_set(mqttClient, onConnect);
  mosquitto_publish_callback_set(mqttClient, onPublish);
  mosquitto_message_callback_set(mqttClient, onMessage);

  mqttRes = mosquitto_username_pw_set(mqttClient, azureUserName, pwd);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_username_pw_set OK.\n");
  }
  else
  {
    printf_s("mosquitto_username_pw_set ERROR: %s\n", mosquitto_strerror(mqttRes));
    goto mqttCreationFailed;
  }

  /* configure CERT */
  mqttRes = mosquitto_tls_opts_set(mqttClient, 1, "tlsv1.2", NULL);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_tls_opts_set OK.\n");
  }
  else
  {
    printf_s("mosquitto_tls_opts_set ERROR: %s\n", mosquitto_strerror(mqttRes));
    goto mqttCreationFailed;
  }

  mqttRes = mosquitto_tls_set(mqttClient, CERT_FILE, NULL, X509_CERT_FILE, X509_KEY_FILE, NULL);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_tls_set OK.\n");
  }
  else
  {
    printf_s("mosquitto_tls_set ERROR: %s\n", mosquitto_strerror(mqttRes));
    goto mqttCreationFailed;
  }
  mqttRes = mosquitto_tls_insecure_set(mqttClient, false);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_tls_insecure_set OK.\n");
  }
  else
  {
    printf_s("mosquitto_tls_insecure_set ERROR: %s\n", mosquitto_strerror(mqttRes));
    goto mqttCreationFailed;
  }

  /* configure MQTT version */
  mqttRes = mosquitto_opts_set(mqttClient, MOSQ_OPT_PROTOCOL_VERSION, &mqttVer);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_opts_set(MOSQ_OPT_PROTOCOL_VERSION) OK\n");
  }
  else
  {
    printf_s("mosquitto_opts_set(MOSQ_OPT_PROTOCOL_VERSION) ERROR\n");
    goto mqttCreationFailed;
  }

  mqttRes = mosquitto_connect(mqttClient, HOST, PORT, MQTT_KEEPALIVE_SECS);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_connect OK\n");
  }
  else
  {
    printf_s("mosquitto_connect ERROR: %s\n", mosquitto_strerror(mqttRes));
    goto mqttCreationFailed;
  }

  return mqttClient;

mqttCreationFailed:
  mqttClientDestroy(mqttClient);
  return NULL;
}

/*******************************************************************************
 *
 * mqttSubscribe - subscribe to MQTT topic
 *
 * This routine subscribes to Provisioning Client Register topics:
 *
 * ERRNO: N/A
 *
 * RETURNS: true if successful, otherwise false
 *
 * \NOMANUAL
 */

static bool mqttSubscribe(struct mosquitto* mqttClient)
{
  int mqttRes;

  mqttRes = mosquitto_subscribe(
      mqttClient, NULL, AZ_IOT_PROVISIONING_CLIENT_REGISTER_SUBSCRIBE_TOPIC, 0);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_subscribe Provisioning Client Register OK\n");
  }
  else
  {
    printf_s(
        "mosquitto_subscribe Provisioning Client Register "
        "ERROR: %s\n",
        mosquitto_strerror(mqttRes));
    return false;
  }

  return true;
}

/*******************************************************************************
 *
 * deviceRegister - publish a registration request
 *
 * This routine publishes a registration request to
 * the Azure IoT Device Provisioning Service to provision a new device to the
 * Azure IoT Hub.
 *
 * ERRNO: N/A
 *
 * RETURNS: true if successful, otherwise false
 *
 * \NOMANUAL
 */

static bool deviceRegister(
    az_iot_provisioning_client* azureClient, /* provisioning client obj */
    struct mosquitto* mqttClient /* mqtt client */
)
{
  char registerTopic[128];
  int msgId;

  if (az_result_succeeded(az_iot_provisioning_client_register_get_publish_topic(
          azureClient, registerTopic, sizeof(registerTopic), NULL)))
  {
    printf_s("az_iot_provisioning_client_register_get_publish_topic OK\n");
  }
  else
  {
    printf_s("az_iot_provisioning_client_register_get_publish_topic "
             "ERROR\n");
    return false;
  }
  return mqttPublish(mqttClient, &msgId, registerTopic, 0, NULL);
}

/*******************************************************************************
 *
 * main - main function of a Real Time Process
 *
 * This sample runs as a RTP. It provisions a new device to the Azure IoT Hub
 * either with SAS or X509.
 * It exits if the provisioning is completed no matter the result.
 *
 * ERRNO: N/A
 *
 * RETURNS: 0 if successful, otherwise 1
 *
 * \NOMANUAL
 */

int main(void)
{
  struct mosquitto* mqttClient = NULL;
  az_iot_provisioning_client azureClient;
  char azureUserName[128];
  char azureClientId[128];
  int mqttRes;
  int ret = 0;

  /* initialize and configure Azure Provisioning Client object */
  if (!azureProvisioningClientCreate(
          &azureClient, azureUserName, sizeof(azureUserName), azureClientId, sizeof(azureClientId)))
  {
    ret = 1;
    goto failed;
  }

  /* initialize MQTT object and connect to Azure IoT DPS */
  mqttClient = mqttClientCreate(&azureClient, azureUserName, azureClientId);
  if (mqttClient == NULL)
  {
    ret = 1;
    goto failed;
  }

  if (!mqttSubscribe(mqttClient))
  {
    ret = 1;
    goto failed;
  }

  if (!deviceRegister(&azureClient, mqttClient))
  {
    ret = 1;
    goto failed;
  }

  mqttRes = mosquitto_loop_forever(mqttClient, -1, 1);
  if (mqttRes == MOSQ_ERR_SUCCESS)
  {
    printf_s("mosquitto_loop_forever OK.\n");
  }
  else
  {
    printf_s("mosquitto_loop_forever ERROR: %s\n", mosquitto_strerror(mqttRes));
  }
failed:
  mqttClientDestroy(mqttClient);
  printf_s("Azure Provisioning Client Sample done!\n");
  return ret;
}